// 导入类型定义
import { ErrorInfo, SDKConfig } from '../types'; // 导入错误信息和SDK配置类型
import {
  getCurrentUrl, // 导入：获取当前页面URL的函数
  getUserAgent, // 导入：获取用户代理（User Agent）的函数
  formatErrorStack, // 导入：格式化错误堆栈的函数
  shouldSample // 导入：判断是否应该根据采样率进行采样的函数
} from '../utils'; // 从工具函数模块导入

// 错误监控器类，负责采集和处理各种前端错误
export class ErrorMonitor {
  private config: SDKConfig; // SDK配置对象
  private errorHandler: (error: ErrorInfo) => void; // 错误处理回调函数，当捕获到错误时调用

  // --- 保存原始方法，以便在destroy时能够安全恢复 ---
  private originalConsoleError: (...args: any[]) => void; // 保存原始的 console.error 方法
  private originalConsoleWarn: (...args: any[]) => void; // 保存原始的 console.warn 方法
  private originalXHROpen: (method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null) => void; // 保存原始的 XMLHttpRequest.prototype.open 方法
  private originalXHRSend: (body?: Document | XMLHttpRequestBodyInit | null) => void; // 保存原始的 XMLHttpRequest.prototype.send 方法
  private originalFetch: (input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>; // 保存原始的 window.fetch 方法

  // 构造函数：初始化时传入配置和错误处理回调
  constructor(config: SDKConfig, errorHandler: (error: ErrorInfo) => void) {
    this.config = config; // 保存SDK配置
    this.errorHandler = errorHandler; // 保存错误上报的回调函数

    // 在构造函数中，立即保存所有需要重写的原始方法
    this.originalConsoleError = console.error; // 保存 console.error
    this.originalConsoleWarn = console.warn; // 保存 console.warn
    this.originalXHROpen = XMLHttpRequest.prototype.open; // 保存 XHR open
    this.originalXHRSend = XMLHttpRequest.prototype.send; // 保存 XHR send
    this.originalFetch = window.fetch; // 保存 fetch
  }

  // --- 将事件处理函数定义为类的属性（箭头函数），以保证 'this' 指向正确，并方便移除监听器 ---

  // JavaScript运行时错误（window.onerror）的处理函数
  private jsErrorHandler = (event: ErrorEvent): void => {
    // 资源加载错误也会触发 window.onerror，但我们希望在 resourceErrorHandler 中统一处理。
    // 通过判断 event.target 是否为元素且非 window 对象，来将其过滤掉。
    if (event.target && event.target !== window) {
      return; // 如果是资源加载错误，则忽略，交由 resourceErrorHandler 处理
    }

    if (!shouldSample(this.config.sampleRate)) {
      return; // 根据采样率判断是否需要处理，如果不需要则直接返回
    }

    // 构建错误信息对象
    const errorInfo: ErrorInfo = {
      type: 'js', // 错误类型：JavaScript 错误
      message: event.message || 'Unknown JavaScript error', // 错误消息
      stack: event.error?.stack || '', // 错误堆栈
      filename: event.filename || '', // 发生错误的文件名
      lineno: event.lineno || 0, // 发生错误的行号
      colno: event.colno || 0, // 发生错误的列号
      timestamp: Date.now(), // 错误发生的时间戳
      url: getCurrentUrl(), // 错误发生的页面URL
      userAgent: getUserAgent(), // 用户代理信息
      extra: { // 附加信息
        error: event.error // 保存原始的错误对象
      }
    };
    this.errorHandler(errorInfo); // 调用上报回调函数
  }

  // 未处理的Promise Rejection错误的处理函数
  private promiseErrorHandler = (event: PromiseRejectionEvent): void => {
    if (!shouldSample(this.config.sampleRate)) {
      return; // 根据采样率判断是否需要处理
    }
    // 构建错误信息对象
    const errorInfo: ErrorInfo = {
      type: 'promise', // 错误类型：Promise 错误
      message: event.reason?.message || event.reason || 'Unhandled Promise rejection', // 错误消息，从 reason 中提取
      stack: event.reason?.stack || '', // 错误堆栈
      timestamp: Date.now(), // 时间戳
      url: getCurrentUrl(), // 页面URL
      userAgent: getUserAgent(), // 用户代理
      extra: { // 附加信息
        reason: event.reason // 保存原始的 reason 对象
      }
    };
    this.errorHandler(errorInfo); // 调用上报回调函数
  }

  // 资源加载错误的处理函数
  private resourceErrorHandler = (event: ErrorEvent): void => {
    // 通过判断 event.target 是否存在且非 window 对象，来专门处理资源加载错误
    if (event.target && event.target !== window) {
      if (!shouldSample(this.config.sampleRate)) {
        return; // 根据采样率判断是否需要处理
      }
      const target = event.target as HTMLElement; // 将事件目标断言为HTML元素
      // 构建错误信息对象
      const errorInfo: ErrorInfo = {
        type: 'resource', // 错误类型：资源加载错误
        message: `Failed to load resource: ${target.tagName}`, // 错误消息，指明加载失败的标签类型
        timestamp: Date.now(), // 时间戳
        url: getCurrentUrl(), // 页面URL
        userAgent: getUserAgent(), // 用户代理
        extra: { // 附加信息
          tagName: target.tagName, // 资源标签名 (e.g., 'IMG', 'SCRIPT')
          src: (target as HTMLImageElement).src || (target as HTMLScriptElement).src || (target as HTMLLinkElement).href, // 资源的URL
          outerHTML: target.outerHTML, // 元素的outerHTML，方便调试
        }
      };
      this.errorHandler(errorInfo); // 调用上报回调函数
    }
  }

  // 初始化错误监控，在此方法中启动所有监听
  public init(): void {
    this.setupJsErrorHandler(); // 启动对JS错误的监听
    this.setupPromiseErrorHandler(); // 启动对Promise错误的监听
    this.setupResourceErrorHandler(); // 启动对资源加载错误的监听
    this.setupHttpErrorHandler(); // 启动对HTTP请求错误的监听
    if (this.config.enableConsole) { // 如果配置中启用了console监控
      this.setupConsoleErrorHandler(); // 则启动对console.error/warn的监听
    }
  }

  // 设置JavaScript错误处理器的具体实现：添加监听器
  private setupJsErrorHandler(): void {
    window.addEventListener('error', this.jsErrorHandler, true); // 在捕获阶段监听 'error' 事件
  }

  // 设置Promise错误处理器的具体实现：添加监听器
  private setupPromiseErrorHandler(): void {
    window.addEventListener('unhandledrejection', this.promiseErrorHandler); // 监听 'unhandledrejection' 事件
  }

  // 设置资源加载错误处理器的具体实现：添加监听器
  private setupResourceErrorHandler(): void {
    // 资源加载错误也会触发 window.onerror, 所以也在捕获阶段监听 'error' 事件来处理
    window.addEventListener('error', this.resourceErrorHandler, true); // 在捕获阶段监听 'error' 事件
  }

  // 设置HTTP请求错误处理器的具体实现：重写原生方法
  private setupHttpErrorHandler(): void {
    const self = this; // 保存 'this' 的引用，以便在重写的方法内部使用

    // --- 拦截 XMLHttpRequest ---
    XMLHttpRequest.prototype.open = function (method: string, url: string, async: boolean = true, username?: string | null, password?: string | null) {
      (this as any)._xhrMethod = method; // 在XHR对象上附加请求方法，供后续使用
      (this as any)._xhrUrl = url; // 在XHR对象上附加请求URL，供后续使用
      return self.originalXHROpen.call(this, method, url, async, username, password); // 调用原始的 open 方法
    };
    XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {
      // 监听 'error' 事件，这通常表示网络层面的错误 (e.g., DNS, CORS)
      this.addEventListener('error', () => {
        if (!shouldSample(self.config.sampleRate)) { return; } // 采样判断
        const errorInfo: ErrorInfo = { // 构建错误对象
          type: 'http', message: `XHR Network Error: ${(this as any)._xhrMethod} ${(this as any)._xhrUrl}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
          extra: { method: (this as any)._xhrMethod, url: (this as any)._xhrUrl, status: this.status, statusText: this.statusText }
        };
        self.errorHandler(errorInfo); // 上报错误
      });
      // 监听 'load' 事件，在请求完成后检查HTTP状态码
      this.addEventListener('load', () => {
        if (this.status >= 400 && this.status < 600) { // 如果状态码表示客户端或服务器错误
          if (!shouldSample(self.config.sampleRate)) { return; } // 采样判断
          const errorInfo: ErrorInfo = { // 构建错误对象
            type: 'http', message: `XHR HTTP Error: ${this.status} at ${(this as any)._xhrUrl}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
            extra: { method: (this as any)._xhrMethod, url: (this as any)._xhrUrl, status: this.status, statusText: this.statusText }
          };
          self.errorHandler(errorInfo); // 上报错误
        }
      });
      return self.originalXHRSend.call(this, body); // 调用原始的 send 方法
    };

    // --- 拦截 Fetch API ---
    window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
      // 使用 .catch() 来捕获网络错误 (e.g., DNS, CORS, a timeout)
      const response = await self.originalFetch(input, init).catch(error => {
        if (shouldSample(self.config.sampleRate)) { // 采样判断
          const errorInfo: ErrorInfo = { // 构建错误对象
            type: 'http', message: `Fetch Network Error: ${error.message}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
            extra: { method: init?.method || 'GET', url: typeof input === 'string' ? input : (input as Request).url, error: error }
          };
          self.errorHandler(errorInfo); // 上报错误
        }
        throw error; // 必须将错误重新抛出，以保证原始的 Promise chain 行为
      });
      // 检查HTTP响应状态码
      if (!response.ok) { // response.ok 为 false 表示状态码不在 200-299 范围内
        if (shouldSample(this.config.sampleRate)) { // 采样判断
          const errorInfo: ErrorInfo = { // 构建错误对象
            type: 'http', message: `Fetch HTTP Error: ${response.status} ${response.statusText}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
            extra: { method: init?.method || 'GET', url: response.url, status: response.status, statusText: response.statusText }
          };
          this.errorHandler(errorInfo); // 上报错误
        }
      }
      return response; // 返回原始的 response 对象
    };
  }

  // 设置控制台错误处理器的具体实现：重写原生方法
  private setupConsoleErrorHandler(): void {
    const self = this; // 保存 'this' 的引用
    // 重写 console.error
    console.error = (...args: any[]) => {
      if (shouldSample(this.config.sampleRate)) { // 采样判断
        const errorInfo: ErrorInfo = { // 构建错误对象
          type: 'custom', message: `Console Error: ${args.map(arg => String(arg)).join(' ')}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
          extra: { consoleArgs: args } // 保存原始的 console 参数
        };
        this.errorHandler(errorInfo); // 上报错误
      }
      self.originalConsoleError.apply(console, args); // 使用保存的原始方法调用，以保证原有功能
    };
    // 重写 console.warn
    console.warn = (...args: any[]) => {
      if (shouldSample(this.config.sampleRate)) { // 采样判断
        const errorInfo: ErrorInfo = { // 构建错误对象
          type: 'custom', message: `Console Warning: ${args.map(arg => String(arg)).join(' ')}`, timestamp: Date.now(), url: getCurrentUrl(), userAgent: getUserAgent(),
          extra: { consoleArgs: args } // 保存原始的 console 参数
        };
        this.errorHandler(errorInfo); // 上报错误
      }
      self.originalConsoleWarn.apply(console, args); // 使用保存的原始方法调用，以保证原有功能
    };
  }

  // 手动上报错误的公共方法
  public reportError(error: Error | string, extra?: Record<string, any>): void {
    if (!shouldSample(this.config.sampleRate)) {
      return; // 采样判断
    }
    const errorInfo: ErrorInfo = { // 构建错误对象
      type: 'custom', // 错误类型：自定义上报
      message: typeof error === 'string' ? error : error.message, // 错误消息
      stack: typeof error === 'string' ? '' : formatErrorStack(error), // 错误堆栈
      timestamp: Date.now(), // 时间戳
      url: getCurrentUrl(), // 页面URL
      userAgent: getUserAgent(), // 用户代理
      extra: extra // 附加信息
    };
    this.errorHandler(errorInfo); // 上报错误
  }

  // 销毁错误监控器，移除所有监听并恢复所有被重写的方法
  public destroy(): void {
    // 移除所有事件监听器
    window.removeEventListener('error', this.jsErrorHandler, true); // 移除JS错误监听
    window.removeEventListener('error', this.resourceErrorHandler, true); // 移除资源错误监听
    window.removeEventListener('unhandledrejection', this.promiseErrorHandler); // 移除Promise错误监听

    // 恢复所有被重写的方法
    if (this.config.enableConsole) { // 如果开启了console监控
      console.error = this.originalConsoleError; // 恢复原始的 console.error
      console.warn = this.originalConsoleWarn; // 恢复原始的 console.warn
    }
    XMLHttpRequest.prototype.open = this.originalXHROpen; // 恢复原始的 XHR open
    XMLHttpRequest.prototype.send = this.originalXHRSend; // 恢复原始的 XHR send
    window.fetch = this.originalFetch; // 恢复原始的 fetch
  }
}
